﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

// Originally from http://stackoverflow.com/questions/861409/wpf-making-hyperlinks-clickable
namespace Hibiku.Helpers
{
    public static class TweetTextBlockRenderer
    {
        // Copied from http://geekswithblogs.net/casualjim/archive/2005/12/01/61722.aspx
        private static readonly Regex s_urlPattern = new Regex(@"(?#Protocol)(?:(?:ht|f)tp(?:s?)\:\/\/|~/|/)?(?#Username:Password)(?:\w+:\w+@)?(?#Subdomains)(?:(?:[-\w]+\.)+(?#TopLevel Domains)(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2}))(?#Port)(?::[\d]{1,5})?(?#Directories)(?:(?:(?:/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|/)+|\?|#)?(?#Query)(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)?");

        private static readonly Regex s_userRefPattern = new Regex(@"@\w+");
        private static readonly Regex s_hashTagPattern = new Regex(@"#\w+");

        public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached(
            "Text",
            typeof(string),
            typeof(TweetTextBlockRenderer),
            new PropertyMetadata(null, OnTextChanged)
        );

        public static string GetText(DependencyObject obj)
        {
            return obj.GetValue(TextProperty) as string;
        }

        public static void SetText(DependencyObject obj, string value)
        {
            obj.SetValue(TextProperty, value);
        }

        private static void OnTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            var textBlock = obj as TextBlock;
            if (textBlock == null)
            {
                return;
            }

            var text = (string)args.NewValue;
            if (string.IsNullOrEmpty(text))
            {
                return;
            }

            var pairs = new Dictionary<Regex, Func<string, Uri>>()
            {
                {s_urlPattern, value => new Uri(value.Contains("://") ? value : "http://" + value)},
                {s_userRefPattern, value => new Uri("http://twitter.com/" + value.Substring(1))},
                {s_hashTagPattern, value => new Uri("http://search.twitter.com/search?q=" + Uri.EscapeDataString(value))},
            };
            IEnumerable<Inline> inlines = new Inline[] { new Run(text) };

            foreach (var pair in pairs)
            {
                try
                {
                    var newInlines = GetInlines(inlines, pair.Key, pair.Value);

                    inlines = newInlines;
                }
                catch (Exception ex)
                {
                    Trace.TraceWarning(string.Format("Failed to add inlines (error = '{0}', text = '{1}')", ex.Message, text));
                }
            }

            textBlock.Inlines.Clear();
            textBlock.Inlines.AddRange(inlines);
        }

        private static IEnumerable<Inline> GetInlines(IEnumerable<Inline> inlines, Regex pattern, Func<string, Uri> uriMaker)
        {
            var newInlines = new List<Inline>(inlines.Count() * 2);

            foreach (var inline in inlines)
            {
                var run = inline as Run;

                if (run != null && pattern.IsMatch(run.Text))
                {
                    // Find all URLs using a regular expression
                    int lastPos = 0;
                    foreach (Match match in pattern.Matches(run.Text))
                    {
                        // Copy raw string from the last position up to the match
                        if (match.Index != lastPos)
                        {
                            var raw_text = run.Text.Substring(lastPos, match.Index - lastPos);
                            newInlines.Add(new Run(raw_text));
                        }

                        // Create a hyperlink for the match
                        var link = new Hyperlink(new Run(match.Value))
                        {
                            NavigateUri = uriMaker(match.Value)
                        };
                        link.Click += OnUrlClick;

                        newInlines.Add(link);

                        // Update the last matched position
                        lastPos = match.Index + match.Length;
                    }

                    // Finally, copy the remainder of the string
                    if (lastPos < run.Text.Length)
                    {
                        newInlines.Add(new Run(run.Text.Substring(lastPos)));
                    }
                }
                else
                {
                    newInlines.Add(inline);
                }
            }

            return newInlines;
        }

        private static void OnUrlClick(object sender, RoutedEventArgs e)
        {
            var link = (Hyperlink)sender;

            Process.Start(link.NavigateUri.ToString());
        }
    }
}
